// Unified Hotkey Manager
// - registerAction(name, handler): register an action callback
// - registerShortcut({ combo, action, preventDefault?, allowInInputs?, when? }): returns disposer
// - setScope(name): optional, not used yet but reserved for future
// - initHotkeys(): start global listeners (call once at app start)

const state = {
  actions: new Map(),
  shortcuts: [],
  enabled: true,
  scope: 'global',
  isMac: typeof navigator !== 'undefined' && /mac/i.test(navigator.platform),
  listenerBound: false,
};

export function initHotkeys() {
  if (state.listenerBound || typeof window === 'undefined') return;
  window.addEventListener('keydown', onKeyDown, { capture: true });
  state.listenerBound = true;
}

export function setScope(scope) {
  state.scope = scope || 'global';
}

export function enableHotkeys(v = true) { state.enabled = !!v; }

export function registerAction(name, handler) {
  state.actions.set(name, handler);
  return () => state.actions.delete(name);
}

export function registerShortcut(opts) {
  const cfg = normalizeShortcut(opts);
  state.shortcuts.push(cfg);
  return () => {
    const idx = state.shortcuts.indexOf(cfg);
    if (idx >= 0) state.shortcuts.splice(idx, 1);
  };
}

export function clearShortcuts() {
  state.shortcuts.length = 0;
}

export function triggerAction(name, event) {
  const fn = state.actions.get(name);
  if (fn) {
    try {
      fn(event);
      return true;
    } catch (e) {
      console.warn(`Action "${name}" failed:`, e);
    }
  }
  return false;
}

// ---------- Utilities for UI (platform mapping and validation)
const ORDER = ['ctrl', 'meta', 'alt', 'shift'];
const NAME_MAP = { ctrl: 'Ctrl', meta: state.isMac ? 'Cmd' : 'Win', alt: state.isMac ? 'Option' : 'Alt', shift: 'Shift' };
const DISPLAY_JOIN = '+';

export function canonicalizeCombo(input) {
  if (!input) return '';
  const parts = String(input).toLowerCase().split('+').map(s => s.trim()).filter(Boolean);
  const mods = new Set();
  let key = '';
  for (const p of parts) {
    if (p === 'mod') mods.add(state.isMac ? 'meta' : 'ctrl');
    else if (['meta','cmd','command','win','super'].includes(p)) mods.add('meta');
    else if (['ctrl','control'].includes(p)) mods.add('ctrl');
    else if (['alt','option'].includes(p)) mods.add('alt');
    else if (p === 'shift') mods.add('shift');
    else key = p;
  }
  const ordered = ORDER.filter(m => mods.has(m));
  return [...ordered, key].filter(Boolean).join('+');
}

export function renderComboForDisplay(input) {
  const canon = canonicalizeCombo(input);
  if (!canon) return '';
  const parts = canon.split('+');
  const mods = parts.slice(0, -1);
  const key = parts.slice(-1)[0];
  const humanMods = mods.map(m => NAME_MAP[m] || m);
  const humanKey = key.length === 1 ? key.toUpperCase() : key.toUpperCase();
  return [...humanMods, humanKey].join(DISPLAY_JOIN);
}

export function isValidCombo(input) {
  if (!input) return false;
  const canon = canonicalizeCombo(input);
  if (!canon) return false;
  const parts = canon.split('+');
  const key = parts.slice(-1)[0];
  if (!key) return false;
  // reject if key is a pure modifier
  if (['ctrl','meta','alt','shift','mod'].includes(key)) return false;
  // basic allowlist for special keys
  if (key.length === 1) return true;
  const specials = new Set(['enter','escape','esc','backspace','delete','home','end','pageup','pagedown','tab','space','arrowup','arrowdown','arrowleft','arrowright']);
  if (specials.has(key)) return true;
  if (/^f\d{1,2}$/.test(key)) return true;
  return true; // permissive by default
}

function normalizeShortcut({ combo, action, preventDefault = true, allowInInputs = false, when = null }) {
  const parsed = parseCombo(combo);
  return { combo, parsed, action, preventDefault, allowInInputs, when };
}

function parseCombo(combo) {
  // supports forms like: 'mod+,', 'ctrl+shift+p', 'escape'
  const parts = String(combo).toLowerCase().split('+').map(s => s.trim()).filter(Boolean);
  const mods = new Set();
  let key = '';
  for (const p of parts) {
    if (p === 'mod') mods.add(state.isMac ? 'meta' : 'ctrl');
    else if (p === 'cmd' || p === 'meta') mods.add('meta');
    else if (p === 'ctrl' || p === 'control') mods.add('ctrl');
    else if (p === 'alt' || p === 'option') mods.add('alt');
    else if (p === 'shift') mods.add('shift');
    else key = p;
  }
  return { mods, key };
}

function eventToKey(e) {
  const k = e.key.length === 1 ? e.key.toLowerCase() : e.key.toLowerCase();
  const mods = new Set();
  if (e.ctrlKey) mods.add('ctrl');
  if (e.metaKey) mods.add('meta');
  if (e.altKey) mods.add('alt');
  if (e.shiftKey) mods.add('shift');
  return { key: k, mods };
}

function matchShortcut(eventSpec, shortcutSpec) {
  // require key match and required modifiers present; no extra modifiers than specified
  if (shortcutSpec.key && eventSpec.key !== shortcutSpec.key) return false;
  for (const m of shortcutSpec.mods) if (!eventSpec.mods.has(m)) return false;
  // ensure no extra modifiers beyond specified
  for (const m of eventSpec.mods) if (!shortcutSpec.mods.has(m)) return false;
  return true;
}

function isEditableTarget(target) {
  if (!target) return false;
  const el = target.closest ? target.closest('input, textarea, [contenteditable="true"]') : null;
  return !!el;
}

function onKeyDown(e) {
  if (!state.enabled) return;
  const spec = eventToKey(e);
  // iterate last registered first for override semantics
  for (let i = state.shortcuts.length - 1; i >= 0; i--) {
    const sc = state.shortcuts[i];
    if (!sc) continue;
    if (!sc.allowInInputs && isEditableTarget(e.target)) continue;
    if (sc.when && !safeEvalWhen(sc.when)) continue;
    if (matchShortcut(spec, sc.parsed)) {
      const fn = state.actions.get(sc.action);
      if (fn) {
        try { fn(e); } catch {}
        if (sc.preventDefault) {
          e.preventDefault();
          e.stopPropagation();
        }
        return;
      }
    }
  }
}

function safeEvalWhen(when) {
  try { return typeof when === 'function' ? !!when() : !!when; } catch { return false; }
}


